<?php


namespace Library\Components;


use Library\Exceptions\ContainerException;
use Library\Exceptions\ContainerNotFoundException;
use Psr\Container\ContainerInterface;

class Container implements ContainerInterface
{
    // 用于保存依赖的定义, 已对象名称为键
    private $definitions = [];

    // 用于缓存ReflectionClass对象, 以对象名称为键
    private $reflections = [];

    // 用于缓存依赖信息, 以对象名为键
    private $dependencies = [];

    public function has($class)
    {
        return isset($this->definitions[$class]);
    }

    /**
     * 给容器 新增依赖定义
     * @param $class
     * @param array $definition
     * @return $this
     * @throws ContainerException
     */
    public function set($class, $definition = [])
    {
        $this->definitions[$class] = $this->normalizeDefinition($class, $definition); // 格式化依赖定义
        return $this; //返回容器对象
    }

    /**
     * @param $class
     * @param $definition
     * @return array|string[]  返回例子: ["class" => "\App\Https\ExampleClass"]
     * @throws ContainerException
     */
    protected function normalizeDefinition($class, $definition)
    {
        // $definition 是空的转换成 ['class' => $class] 形式
        if (empty($definition)) {
            return ['class' => $class];
        } elseif (is_string($definition)) {
            return ['class' => $definition];
        } elseif (is_object($definition)) {
            return $definition;
        } elseif (is_array($definition)) {
            if (!isset($definition['class'])) {
                $definition['class'] = $class;
            }
            return $definition;
        } else {
            throw new ContainerException("不支持类型: {$class} :" . gettype($definition));
        }
    }

    public function get($class)
    {
        if (!isset($this->definitions[$class])) { // 如果没有该依赖定义
            return $this->build($class, []);
        }

        $definition = $this->definitions[$class]; // 获取当前类的依赖定义
        if (is_array($definition)) { // 一般来说都应该为数组
            $concrete = $definition['class'];  // 这里一般都是class名: 如\App\Https\DemoController
            unset($definition['class']);  // 清理无用对象

            if ($concrete === $class) {
                $object = $this->build($class, $definition);
            } else {
                $object = $this->get($concrete);
            }
        } elseif (is_object($definition)) {
            return $this->_singletons[$class] = $definition;
        } else {
            throw new ContainerNotFoundException("不能识别的对象类型: " . gettype($definition));
        }

        return $object;
    }

    /**
     * 构建方法
     * @param $class
     * @param $definition
     */
    public function build($class, $definition)
    {

        try {// 获取反射对象
            $reflector = $this->getReflectionClass($class); // 获取依赖关系数组
            $dependencies = $this->getDependencies($class, $reflector); // 创建一个类的新实例
            $reflector = $reflector->newInstanceArgs($dependencies);
        } catch (\Throwable $e) {
            throw new ContainerException("反射错误");
        }

        return $reflector;
    }

    /**
     * @param $class
     * 返回反射对象
     */
    public function getReflectionClass($class)
    {
        // 首先判断该类的反射对象是否已经存在
        if (isset($this->reflections[$class])) {
            return $this->reflections[$class];
        }

        // 如果反射对象不存在
        $reflection = new \ReflectionClass($class);

        if (!$reflection->isInstantiable()) { // 判断该类是否可以实例化
            throw new ContainerException("不能实例化: " . $class);
        }

        return $this->reflections[$class] = $reflection;
    }

    /**
     * 获取类的依赖关系
     * @param $class
     * @param $reflector
     */
    public function getDependencies($class, $reflector)
    {
        // 判断该类是否有依赖关系
        if (isset($this->dependencies[$class])) {
            return $this->dependencies[$class]; // 如果有直接返回
        }


        $constructor = $reflector->getConstructor();

        // 如果没有构造函数, 直接实例化返回
        if (is_null($constructor)) {
            return $this->dependencies[$class] = [];
        }

        $parameters = $constructor->getParameters();

        $dependencies = [];
        foreach ($parameters as $className) {
            $dependency = $className->getClass();

            if (is_null($dependency)) {
                $dependencies[] = $this->resolveNoneClass($className);
            } else {
                // 取出容器中绑定的类, 否则自动绑定
                $dependencies[] = $this->get($dependency->getName());
            }
        }
        $this->dependencies[$class] = $dependencies;

        return $dependencies;
    }

    public function resolveNoneClass($class) {
        if ($class->isDefaultValueAvailable()) {
            return $class->getDefaultValue();
        }

        throw new ContainerException("不能解析参数");
    }
}